package com.qire.antsbinder.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class TypeUtils {

    /**
     * 获取类上指定位置的泛型类型
     * @param subclass  对象类型
     * @param index     泛型表中第几个泛型
     * @return 对应位置上的泛型类
     */
    public static Type getSuperclassTypeParameter(Class<?> subclass,int index){
        Type[] types = getSuperclassTypeParameter(subclass);
        if(types != null && types.length > 0 && index < types.length) {
            return types[index];
        }
        return null;
    }

    /**
     * 获取类上泛型类型列表
     * @param subclass 对象类型
     * @return 如果存在则返回泛型类型数组
     */
    public static Type[] getSuperclassTypeParameter(Class<?> subclass) {
        Type superclass = subclass.getGenericSuperclass();
        if (superclass instanceof Class) {
            throw new RuntimeException("目标Class上没有泛型类型");
        }
        ParameterizedType parameterized = (ParameterizedType) superclass;
        return parameterized.getActualTypeArguments();
    }

    /**
     * 检查是否存在泛型类型
     * @return 存在返回true，否则false
     */
    public static boolean existGenericType(Field field){
        Type type = field.getGenericType();
        return null != type && type instanceof ParameterizedType;
    }

    /**
     * 获取字段指定位置的真实泛型类型
     * @param field 目标字段
     * @param index 泛型表中第几个泛型
     * @return 泛型类型
     */
    public static Type getFieldActualGenericType(Field field,int index){
        Type[] types = getFieldActualGenericType(field);
        if(types != null && types.length > 0 && index < types.length) {
            return types[index];
        }
        return null;
    }

    /**
     * 获取字段真实的泛型类型
     * @param field 目标字段
     * @return 泛型类型列表
     */
    public static Type[] getFieldActualGenericType(Field field){
        Type type = field.getGenericType();
        if(null != type && type instanceof ParameterizedType) {
            return ((ParameterizedType) type).getActualTypeArguments();
        }
        return null;
    }

    /**
     * 查找指定 {@code method} 的参数列表中各个参数包含的注解集合，通过注解类型 {@code annotationClass} 来过滤；
     * 目前没有做方法与注解列表映射，所以，默认只会获取参数的注解列表中第一个符合条件的注解。
     * 如果希望获取全部，一个提供一个map容器返回的方法来对应 参数索引位置与注解列表的对应关系。
     *
     * 与 {@link #findParameterAnnotationByType} 的区别是前者是通过描述注解的注解来过滤获取的集合，这个方法只能查找指定用于直接修饰参数的注解类型。
     * 可以查找描述在方法参数上的 {@code annotationClass} 类型注解，只返回这一个类型的注解。
     *
     * @param method 要获取参数名的方法
     * @param annotationClass 指定关注的注解类型
     * @return 按参数顺序排列的参数注解列表
     */
    public static <T extends Annotation> List<T> findParameterAnnotationBy(Method method, Class<T> annotationClass) {
        // 2维数组中，一维代表了参数，二维代表了一维参数的所有注解集合。
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) {
            return null;
        }
        List<T> dstAnnotations = new ArrayList<>();
        for (Annotation[] parameterAnnotation : parameterAnnotations) { // 遍历方法的所有参数
            for (Annotation annotation : parameterAnnotation) { // 遍历参数的所有注解
                if(annotationClass.isInstance(annotation)){
                    dstAnnotations.add((T) annotation);
                    break;
                }
            }
        }
        return dstAnnotations;
    }

    /**
     * 查找指定 {@code method} 的参数列表中各个参数包含的注解集合，通过被注解类型 {@code annotationTypeClass} 描述过的注解来过滤；
     * 目前没有做方法与注解列表映射，所以，默认只会获取参数的注解列表中第一个符合条件的注解。
     * 如果希望获取全部，一个提供一个map容器返回的方法来对应 参数索引位置与注解列表的对应关系。
     *
     * 与 {@link #findParameterAnnotationBy} 的区别是前者是通过注解类本身来查找过滤，而这个方法是通过标记注解的注解来过滤多个注解的集合，
     * 可以查找被 {@code annotationTypeClass} 描述过的多个注解类型。
     *
     * @param method 要获取参数名的方法
     * @param annotationTypeClass 描述一类注解的注解类型
     * @return 按参数顺序排列的参数注解列表
     */
    public static List<? extends Annotation> findParameterAnnotationByType(Method method, Class<? extends Annotation> annotationTypeClass) {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) {
            return null;
        }
        List<Annotation> dstAnnotations = new ArrayList<>();
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            for (Annotation annotation : parameterAnnotation) {
                if(annotation.annotationType().isAnnotationPresent(annotationTypeClass)) {
                    dstAnnotations.add(annotation);
                    break;
                }
            }
        }
        return dstAnnotations;
    }

}
